import { isInstanceOf } from "@reacord/helpers/is-instance-of"
import { randomUUID } from "node:crypto"
import type { ReactNode } from "react"
import { ReacordElement } from "../../internal/element.js"
import type { ComponentInteraction } from "../../internal/interaction"
import type {
	ActionRow,
	ActionRowItem,
	MessageOptions,
} from "../../internal/message"
import { Node } from "../../internal/node.js"
import type { ComponentEvent } from "../component-event"
import { OptionNode } from "./option-node"
import { omit } from "@reacord/helpers/omit.js"

/** @category Select */
export interface SelectProps {
	children?: ReactNode
	/** Sets the currently selected value */
	value?: string

	/** Sets the currently selected values, for use with `multiple` */
	values?: string[]

	/** The text shown when no value is selected */
	placeholder?: string

	/** Set to true to allow multiple selected values */
	multiple?: boolean

	/**
	 * With `multiple`, the minimum number of values that can be selected. When
	 * `multiple` is false or not defined, this is always 1.
	 *
	 * This only limits the number of values that can be received by the user.
	 * This does not limit the number of values that can be displayed by you.
	 */
	minValues?: number

	/**
	 * With `multiple`, the maximum number of values that can be selected. When
	 * `multiple` is false or not defined, this is always 1.
	 *
	 * This only limits the number of values that can be received by the user.
	 * This does not limit the number of values that can be displayed by you.
	 */
	maxValues?: number

	/**
	 * When true, the select will be slightly faded, and cannot be interacted
	 * with.
	 */
	disabled?: boolean

	/**
	 * Called when the user inputs a selection. Receives the entire select change
	 * event, which can be used to create new replies, etc.
	 */
	onChange?: (event: SelectChangeEvent) => void

	/**
	 * Convenience shorthand for `onChange`, which receives the first selected
	 * value.
	 */
	onChangeValue?: (value: string, event: SelectChangeEvent) => void

	/** Convenience shorthand for `onChange`, which receives all selected values. */
	onChangeMultiple?: (values: string[], event: SelectChangeEvent) => void
}

/** @category Select */
export type SelectChangeEvent = ComponentEvent & {
	values: string[]
}

/**
 * See [the select menu guide](/guides/select-menu) for a usage example.
 *
 * @category Select
 */
export function Select(props: SelectProps) {
	return (
		<ReacordElement props={props} createNode={() => new SelectNode(props)}>
			{props.children}
		</ReacordElement>
	)
}

class SelectNode extends Node<SelectProps> {
	readonly customId = randomUUID()

	override modifyMessageOptions(message: MessageOptions): void {
		const actionRow: ActionRow = []
		message.actionRows.push(actionRow)

		const options = [...this.children]
			.filter(isInstanceOf(OptionNode))
			.map((node) => node.options)

		const {
			multiple,
			value,
			values,
			minValues = 0,
			maxValues = 25,
			...props
		} = omit(this.props, [
			"children",
			"onChange",
			"onChangeValue",
			"onChangeMultiple",
		])

		const item: ActionRowItem = {
			...props,
			type: "select",
			customId: this.customId,
			options,
			values: [],
		}

		if (multiple) {
			item.minValues = minValues
			item.maxValues = maxValues
			if (values) item.values = values
		}

		if (!multiple && value != undefined) {
			item.values = [value]
		}

		actionRow.push(item)
	}

	override handleComponentInteraction(
		interaction: ComponentInteraction,
	): boolean {
		const isSelectInteraction =
			interaction.type === "select" &&
			interaction.customId === this.customId &&
			!this.props.disabled

		if (!isSelectInteraction) return false

		this.props.onChange?.(interaction.event)
		this.props.onChangeMultiple?.(interaction.event.values, interaction.event)
		if (interaction.event.values[0]) {
			this.props.onChangeValue?.(interaction.event.values[0], interaction.event)
		}
		return true
	}
}
